package com.glink.manage.common.sm;

import lombok.SneakyThrows;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;

/**
 * 国密SM2加密算法
 * 加密方式 sm2p256v1
 * 加密模式共有两种(C1C2C3 和  C1C3C2)，本算法暂时支持 C1C2C3
 *
 * @author qiushaoshan
 * @date 2025-01-07
 * @since 1.8.4.RELEASE
 */
public class Sm2Encryption {
    public static final String DOUBLE_ZERO = "00";
    public static final int DOUBLE_SIX = 66;
    public static final String SM_2_P_256_V_1 = "sm2p256v1";

    private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder();

    private Sm2Encryption() {

    }

    public final static Sm2Encryption createInstance() {
        return new Sm2Encryption();
    }

    /**
     * 生成 SM2 公私钥对
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidAlgorithmParameterException
     */
    public KeyPair geneSM2KeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
        final ECGenParameterSpec sm2Spec = new ECGenParameterSpec(SM_2_P_256_V_1);
        // 获取一个椭圆曲线类型的密钥对生成器
        final KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
        // 使用SM2参数初始化生成器
        kpg.initialize(sm2Spec);
        // 获取密钥对
        KeyPair keyPair = kpg.generateKeyPair();
        return keyPair;
    }

    /**
     * 获取私钥（16进制字符串，头部不带00长度共64）
     *
     * @param privateKey 私钥
     * @return
     */
    public String getPriKeyHexString(PrivateKey privateKey) {
        BCECPrivateKey s = (BCECPrivateKey) privateKey;
        String priKeyHexString = Hex.toHexString(s.getD().toByteArray());
        if (null != priKeyHexString && priKeyHexString.length() == DOUBLE_SIX &&
                DOUBLE_ZERO.equals(priKeyHexString.substring(0, 2))) {
            return priKeyHexString.substring(2);
        }
        return priKeyHexString;
    }

    /**
     * 获取公钥（16进制字符串，头部带04长度共130）
     *
     * @param publicKey
     * @return
     */
    public String getPubKeyHexString(PublicKey publicKey) {
        BCECPublicKey p = (BCECPublicKey) publicKey;
        return Hex.toHexString(p.getQ().getEncoded(false));
    }

    /**
     * SM2加密算法
     *
     * @param publicKey 公钥
     * @param data      明文数据
     * @return
     */
    public String encrypt(PublicKey publicKey, String data) {
        BCECPublicKey p = (BCECPublicKey) publicKey;
        return encrypt(Hex.toHexString(p.getQ().getEncoded(false)), data);
    }

    /**
     * SM2解密算法
     *
     * @param privateKey 私钥（16进制字符串）
     * @param cipherData 密文数据
     * @return
     */
    public String decrypt(PrivateKey privateKey, String cipherData) {
        BCECPrivateKey s = (BCECPrivateKey) privateKey;
        return decrypt(Hex.toHexString(s.getD().toByteArray()), cipherData);
    }

    /**
     * SM2加密算法
     *
     * @param pubKeyHexString 公钥（16进制字符串）
     * @param data            明文数据
     * @return
     */
    public String encrypt(String pubKeyHexString, String data) {
        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM_2_P_256_V_1);
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(Hex.decode(pubKeyHexString));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2Engine sm2Engine = new SM2Engine();
        // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes();
            arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("SM2加密时出现异常:" + e.getMessage());
        }
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * SM2解密算法
     *
     * @param priKeyHexString 私钥（16进制字符串）
     * @param cipherData      密文数据
     * @return
     */
    public String decrypt(String priKeyHexString, String cipherData) {

        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (!cipherData.startsWith("04")) {
            cipherData = "04" + cipherData;
        }
        byte[] cipherDataByte = Hex.decode(cipherData);

        //获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName(SM_2_P_256_V_1);
        //构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());

        BigInteger privateKeyD = new BigInteger(priKeyHexString, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        SM2Engine sm2Engine = new SM2Engine();
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);

        String result = "";
        try {
            byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
            return new String(arrayOfBytes);
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("SM2解密时出现异常:" + e.getMessage());
        }
        return result;
    }

    /**
     * SM2解密算法
     *
     * @param privateKey 私钥
     * @param cipherData 密文数据
     * @param mode       密文排列方式
     * @return
     */
    @SneakyThrows
    public static byte[] decrypt(BigInteger privateKey, byte[] cipherData, SM2EngineEnhancer.Mode mode) {
        final ASN1ObjectIdentifier sm2p256v1 = GMObjectIdentifiers.sm2p256v1;
        //获取一条SM2曲线参数
        X9ECParameters parameters = GMNamedCurves.getByOID(sm2p256v1);
        // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
        ECNamedDomainParameters namedDomainParameters = new ECNamedDomainParameters(
                sm2p256v1, parameters.getCurve(), parameters.getG(), parameters.getN());
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKey, namedDomainParameters);
        SM2EngineEnhancer sm2Engine = new SM2EngineEnhancer(mode);
        // 设置sm2为解密模式
        sm2Engine.init(false, privateKeyParameters);
        // 使用BC库加解密时密文以04开头，传入的密文前面没有04则补上
        if (cipherData[0] == 0x04) {
            return sm2Engine.processBlock(cipherData, 0, cipherData.length);
        } else {
            byte[] bytes = new byte[cipherData.length + 1];
            bytes[0] = 0x04;
            System.arraycopy(cipherData, 0, bytes, 1, cipherData.length);
            return sm2Engine.processBlock(bytes, 0, bytes.length);
        }
    }

    public static String decryptBase64(String privateKey, String cipherData) {
        return decryptBase64(privateKey, cipherData, SM2EngineEnhancer.Mode.C1C3C2);
    }

    public static String decryptBase64(String privateKey, String cipherData, SM2EngineEnhancer.Mode mode) {
        final BigInteger key = new BigInteger(BASE64_DECODER.decode(privateKey));
        final byte[] decrypt = decrypt(key, BASE64_DECODER.decode(cipherData), mode);
        return new String(decrypt, StandardCharsets.UTF_8);
    }

    /*public static void main(String[] args) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {

        String M = "6782100554b513b73d96e511d3de1fff";
//        System.out.println("\n明文：" + M);
//
        Sm2Encryption sm2 = new Sm2Encryption();
//        KeyPair keyPair = sm2.geneSM2KeyPair();
//
//        PublicKey publicKey = keyPair.getPublic();
//        String pubKeyHexString = sm2.getPubKeyHexString(publicKey);
//        System.out.println("公钥：" + pubKeyHexString);
//
//        PrivateKey privateKey = keyPair.getPrivate();
//        String priKeyHexString = sm2.getPriKeyHexString(privateKey);
//        System.out.println("私钥：" + priKeyHexString);
        String pubKeyHexString="048252153439b2573123ca349d8f404c93f9747b76a0b41e93197d56235cdf2919f484015e1a397df50b96b02f6220936a9a5b253e169567742823c40d883abf19";

        String cipherData = sm2.encrypt(pubKeyHexString, M);
        System.out.println("密文：" + cipherData);
//        String priKeyHexString = "104983cbf7c71ceaa4ea329e9523d139ca9cc2b22eeb5e6d8bfadad04eaca3e5";
////      String cipherData = "04dbbf5771ffca2becb149ca536054db3e76d54d29335e45ff9f484c70ca6773e61f48671c4cc51cebc57f7c6a43e12bb18c294e41b9349c0f1cb25740f73eaa932d607a0d088d282f90d18b75dfc687b37dabdb6123f067bef859ca211da069d17f1a9979b59c666d2f653a2f20c17646a0907ed4b5cdd76b5963fc29d9430201139c";
////      String cipherData = "041b352280b877a31f02b456bb3732a9a0eb7ae2f4bff9dfff6095eff604d2654afeec17d65234f92b3fb61a52b186aadb241b4ea3a9eb1015f4c471dc9ba61ec68a025becb5b6348245f9ba6ecc48e4c8841cc5292740e6db6229301fd1deb6b6dbaa03398a74d1107bbced362e906f11930301ce8258c3241e64ff90cb0559a26ec5";
//        String cipherData = "9d2064fb49e59cd95a9650fe07dea817121d79512c6203ab946674b728f87ca7cbb8f94cf71a1cf2eebd09a4102e6078e3c4befd3984241c2ae3f1e71ee65f23b6dd19e0e61ccc9a29867e8579fd180e245a8b0268d9e39fc97e146ef1a5ff53875ce3f7ee1699722a65db875b155249e78439c49ae8a17c1973f84e21e7e91eb9c8";
//        String text = sm2.decrypt(priKeyHexString, cipherData);
//        System.out.println("解密：" + text);
    }*/
}
